﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace INSTA.Library
{
    public static class ReflectionHelper
    {



        /// <summary>
        /// Gets the standard bindings.
        /// </summary>
        /// <returns></returns>
        private static BindingFlags GetStandardBindings()
        {
            // on constitue une valeur de BindingFlags 
            // pour avoir les éléments :
            //      statiques + instance
            //      public + non publics

            return BindingFlags.Public |
                        BindingFlags.NonPublic |
                        BindingFlags.Static |
                        BindingFlags.Instance;
        }



        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="target">The target object.</param>
        /// <param name="propertyName">Name of the property.</param>
        public static object GetPropertyValue(this object target, string propertyName)
        {
            // on obtient le type de l'objet..
            var type = target.GetType();
            var flags = GetStandardBindings();
            var result = type.InvokeMember(propertyName,
                                           flags | BindingFlags.GetProperty,
                                           null,    //--> "Binder" -> Allways null
                                           target,
                                           null);

            return result;
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="target">The target object.</param>
        public static T GetPropertyValue<T>(object target, string propertyName) where T : class
        {
            // on obtient le type de l'objet..
            var type = target.GetType();
            var flags = GetStandardBindings();
            var result = type.InvokeMember(propertyName,
                                              flags | BindingFlags.GetProperty,
                                              null,
                                              target,
                                              null);
            // On caste directement la valeur de retour en T..
            return result as T;
        }


        /// <summary>
        /// Sets the property value.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="target">The target.</param>
        /// <param name="propertyValue">The property value.</param>
        public static void SetPropertyValue(this object target, string propertyName, object propertyValue)
        {
            // on obtient le type de l'objet..
            Type type = target.GetType();

            // on constitue une valeur de BindingFlags 
            // pour avoir les éléments :
            //      statiques   +   instance
            //      public      +   non publics
            var flags = GetStandardBindings();

            // et l'on invoque dynamiquement la propriété
            // en écriture, en lui passant la valeur souhaitée
            // sous la forme d'un tableau d'object..
            type.InvokeMember(propertyName,
                                flags | BindingFlags.SetProperty,
                                null,       //----> Binder allways null
                                target,
                                new object[] { propertyValue });
        }

        public static void SetPropertyValue(this Type target, string propertyName, object propertyValue)
        {
            // on obtient le type de l'objet..
            Type type = target;

            // on constitue une valeur de BindingFlags 
            // pour avoir les éléments :
            //      statiques   +   instance
            //      public      +   non publics
            var flags = GetStandardBindings();

            // et l'on invoque dynamiquement la propriété
            // en écriture, en lui passant la valeur souhaitée
            // sous la forme d'un tableau d'object..
            type.InvokeMember(propertyName,
                                flags | BindingFlags.SetProperty,
                                null,       //----> Binder allways null
                                target,
                                new object[] { propertyValue });
        }






















        /// <summary>
        /// Calls the method.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>the value returned by the method, if any.</returns>
        public static object CallMethod(object target,
                                        string methodName,
                                        params object[] parameters)
        {
            // on obtient le type de l'objet..
            Type type = target.GetType();

            // on constitue une valeur de BindingFlags 
            // pour avoir les éléments :
            //      statiques   +   instance
            //      public      +   non publics
            var flags = GetStandardBindings();

            // et l'on invoque dynamiquement la propriété
            // en écriture, en lui passant la valeur souhaitée
            // sous la forme d'un tableau d'object..
            object result = type.InvokeMember(methodName,
                                              flags | BindingFlags.InvokeMethod,
                                              null, //----> Binder allways null
                                              target,
                                              parameters);
            return result;

        }




        /// <summary>
        /// Determines whether the specified property 
        /// is decorated with the specified attribute type. 
        /// </summary>
        /// <typeparam name="T">The attribute type</typeparam>
        /// <param name="property">The property.</param>
        /// <returns>true if the specified PropertyInfo
        /// is decorated with the specified Attribute type.
        /// </returns>
        public static bool IsDecoratedWith<T>(this PropertyInfo property) where T : Attribute
        {
            // On obtient un tableau d'object que l'on
            // recaste en tableau d'Attribute..
            var attributes = (Attribute[])property.GetCustomAttributes(typeof(T), true);
            return attributes.Length > 0;
        }


        /// <summary>
        /// Determines whether the specified property 
        /// is decorated with the specified attribute type. 
        /// </summary>
        /// <typeparam name="T">The attribute type</typeparam>
        /// <param name="type">The property.</param>
        /// <returns>true if the specified PropertyInfo
        /// is decorated with the specified Attribute type.
        /// </returns>
        public static bool IsDecoratedWith<T>(this Type type) where T : Attribute
        {
            // On obtient un tableau d'object que l'on
            // recaste en tableau d'Attribute..
            var attributes = (Attribute[])type.GetCustomAttributes(typeof(T), true);
            return attributes.Length > 0;
        }


        /// <summary>
        /// Gets the unique attribute of the specified type.
        /// </summary>
        /// <typeparam name="T">The attribute type.</typeparam>
        /// <param name="property">The property.</param>
        /// <returns>The unique attribute.</returns>
        public static T GetUniqueAttribute<T>(this PropertyInfo property) where T : Attribute
        {
            object[] attrs = property.GetCustomAttributes(typeof(T), true);

            // on renvoie le 1er élémént du tableau,
            // re-casté vers le type de l'attribut ..            
            return (T)attrs[0];
        }


        /// <summary>
        /// Gets the unique attribute.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public static T GetUniqueAttribute<T>(this Type type) where T : Attribute
        {
            object[] attrs = type.GetCustomAttributes(typeof(T), true);

            // on renvoie le 1er élémént du tableau,
            // re-casté vers le type de l'attribut ..            
            return (T)attrs[0];
        }












        /// <summary>
        /// Returns true if the specified <see cref="Type"/> implements
        /// the specified interface.
        /// </summary>
        /// <typeparam name="TInterfaceType">The interface type to check for.</typeparam>
        /// <param name="type">The class to check.</param>
        /// <returns>true if the class to check implements the specified interface.</returns>
        public static bool ImplementsInterface<TInterfaceType>(this Type type)
        {
            var result = false;

            var implementedInterfaces = (from x in type.GetInterfaces()
                                         where x == typeof(TInterfaceType)
                                         select x).Count();

            result = (implementedInterfaces > 0);

            return result;
        }

        /// <summary>
        /// Returns true if the specified object implements
        /// the specified interface.
        /// </summary>
        /// <typeparam name="TInterfaceType">The interface type to check for.</typeparam>
        /// <param name="target">The object to check.</param>
        /// <returns>true if the object to check implements the specified interface.</returns>
        public static bool ImplementsInterface<TInterfaceType>(this object target)
        {
            return ImplementsInterface<TInterfaceType>(target.GetType());
        }


        /// <summary>
        /// Determines whether the specified Type is derived 
        /// from the type of T.
        /// </summary>
        /// <typeparam name="T">The Type</typeparam>
        /// <param name="typeToTest">The type to test.</param>
        /// <returns>
        /// true if the type to test is derived from T,
        /// else, false.
        /// </returns>        
        private static bool IsDerivedFrom<T>(this Type typeToTest)
        {
            bool result = false;

            if (!typeToTest.IsInterface)
            {
                while (typeToTest.BaseType != typeof(object))
                {
                    if (typeToTest.BaseType == typeof(T))
                    {
                        result = true;
                        break;
                    }
                    typeToTest = typeToTest.BaseType;
                }
            }
            return result;
        }

    }
}

